#include "t_my_httpbackend.h"
#include "t_httpreply.h"


#include <QtGui>
#include <QtNetwork>
#include "nbr.h"
#include <typeinfo>

#include "private/qnoncontiguousbytedevice_p.h"

static QNetworkReply::NetworkError s_networkErrorCodeFromHttpStatus(int httpStatusCode, const QUrl &url);

QMap<QUrl, QString> s_redirect_info;
QMap<QUrl, QString> s_url_info;
QMap<QString, qint64> s_size_info;

QString s_last_video_id = "";
QString s_last_video_fmt = "";

QUrl s_unify_url(QUrl a_url)
{
	QString v_str = a_url.toString();
	if(v_str.endsWith("&"))
	{
		v_str.chop(1);
	}
	return QUrl(v_str);
}

QObject *T_MyBackendFactory::create(QNetworkAccessManager * manager,
                        QNetworkAccessManager::Operation op,
                        const QNetworkRequest & request,
                        QIODevice * outgoingData) const
{
	//qDebug() << "T_MyBackendFactory::create()" << request.url();
	QUrl v_url = request.url();
	QUrl v_unify_url = s_unify_url(v_url);
	QString v_scheme = v_url.scheme().toLower();
	if(v_scheme == "http" || v_scheme == "https")
	{
#if 0x0
		if(v_url.toString().startsWith("http://www.youtube.com/get_video?"))
		{
			//"http://www.youtube.com/get_video?asv=3&video_id=g7_5PBr1i4A&noflv=1&fmt=34&t=vjVQa1PpcFO9CFSSO6DGgqYMhp8kZKxOhInGiXOuId4=&el=detailpage"
			s_last_video_id = v_url.queryItemValue("video_id");
			s_last_video_fmt = v_url.queryItemValue("fmt");
			return 0;
		}
#endif
        if(
                v_url.toString().startsWith("http://localhost")
                ||
                v_url.toString().startsWith("https://localhost")
                )
        {
            T_MyHttpBackend *v_backend = new T_MyHttpBackend(manager);
            //v_backend->manager = manager->d_func();
            return v_backend;
        }
        if(v_url.toString() == "https://www.google.com/youtube/accounts/ClientLogin")
		{
            return new T_MyHttpBackend(manager);
		}
		else if(v_url.toString().startsWith("http://www.youtube.com/watch?"))
        {
			qDebug() << "T_MyBackendFactory::create(0A)" << v_url;
            return new T_MyHttpBackend(manager);
		}
		else if(v_url.toString().startsWith("http://www.youtube.com/profile_ajax?"))
		{
			qDebug() << "T_MyBackendFactory::create(0B)" << v_url;
            return new T_MyHttpBackend(manager);
#if 0x0
            qDebug() << outgoingData;
            qDebug() << outgoingData->bytesAvailable();
            qDebug() << outgoingData->read(1024);
            //return new T_MyHttpBackend;
            QList<QByteArray> headerList = request.rawHeaderList();
            foreach(QByteArray ba, headerList)
            {
                QByteArray headerData = request.rawHeader(ba);
                qDebug() << ba << headerData;
            }
            return 0;
#endif
		}
		else if(v_url.toString().startsWith("http://www.youtube.com/get_video_info?"))
		{
			qDebug() << "T_MyBackendFactory::create(1)" << v_url;
            return new T_MyHttpBackend(manager);
		}
		else if(s_redirect_info.contains(v_unify_url))
		{
			qDebug() << "T_MyBackendFactory::create(2)" << s_url_info.value(v_url) << v_url;
            return new T_MyHttpBackend(manager);
		}
		else if(s_url_info.contains(v_unify_url))
		{
			qDebug() << "T_MyBackendFactory::create(3)" << s_url_info.value(v_url) << v_url;
            return new T_MyHttpBackend(manager);
		}
#if 0x0
		else if(v_url.toString().contains(".youtube.com/videoplayback?"))
		{
			qDebug() << "T_MyBackendFactory::create(): CONTAINS===>" << v_url.toString();
			qDebug() << "T_MyBackendFactory::create(): CONTAINS===>s_last_video_id" << s_last_video_id;
			qDebug() << "T_MyBackendFactory::create(): CONTAINS===>s_last_video_fmt" << s_last_video_fmt;
			QUrl v_unify_url = s_unify_url(v_url);
			s_url_info[v_unify_url] = QString("%1:%2").arg(s_last_video_id).arg(s_last_video_fmt);
			s_redirect_info[v_unify_url] = QString("%1:%2").arg(s_last_video_id).arg(s_last_video_fmt);
			return new T_MyHttpBackend;
		}
#endif
        else if(v_url.toString().contains(".youtube.com/videoplayback?"))
        {
            qDebug() << "T_MyBackendFactory::create(): CONTAINS===>" << v_url.toString();
            return 0;
        }
        else
		{
            //qDebug() << "T_MyBackendFactory::create(): NOT-HANDLED===>" << v_url.toString();
			return 0;
		}
	}
	return 0;
}

T_MyHttpBackend::T_MyHttpBackend(QNetworkAccessManager * manager)
	: QNetworkAccessBackend()
	, m_httpReply(0)
#ifndef QT_NO_OPENSSL
    , m_pendingSslConfiguration(0), m_pendingIgnoreAllSslErrors(false)
#endif
{
    this->manager = manager->d_func();
}

T_MyHttpBackend::~T_MyHttpBackend()
{
	p_disconnectFromHttp();
#ifndef QT_NO_OPENSSL
    delete m_pendingSslConfiguration;
#endif
}

/* virtual */ void T_MyHttpBackend::open()
{
	QNetworkRequest v_request = QNetworkAccessBackend::request();
	QUrl v_request_url = v_request.url();

    if(reply->outgoingData || reply->outgoingDataBuffer)
    {
        m_uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
        m_uploadDevice->setParent(this);
    }
    else
    {
        m_uploadDevice = 0;
    }

	if(v_request_url.toString() == "https://www.google.com/youtube/accounts/ClientLogin")
	{
		qDebug() << "[reply->outgoingData]" << reply->outgoingData;
		qDebug() << reply->outgoingData->bytesAvailable();
	}
	if(QRegExp("^http://[^.]+[.]youtube[.]com/watch[?]").indexIn(v_request_url.toString()) != -1)
	{
		m_httpReply = new T_HttpReply_2(
				QNetworkAccessBackend::operation(),
				v_request,
                m_uploadDevice
				);
	}
	else if(v_request_url.toString().startsWith("http://www.youtube.com/profile_ajax?"))
	{
        if(m_uploadDevice)
        {
            qDebug() << "profile_ajax[m_uploadDevice]" << m_uploadDevice;
            qDebug() << "profile_ajax[m_uploadDevice->bytesAvailable()]" << m_uploadDevice->bytesAvailable();
            QByteArray v_bytes = m_uploadDevice->readAll();
            qDebug() << v_bytes;
            QBuffer * v_buffer = new QBuffer;
            v_buffer->setParent(this);
            v_buffer->open(QIODevice::ReadWrite);
            v_buffer->write(v_bytes);
            v_buffer->reset();
            m_uploadDevice = v_buffer;
        }
		m_httpReply = new T_HttpReply_2(
				QNetworkAccessBackend::operation(),
				v_request,
                m_uploadDevice
                );
	}
	else if(v_request_url.toString().startsWith("http://www.youtube.com/get_video_info?"))
	{
		m_httpReply = new T_HttpReply_2(
				QNetworkAccessBackend::operation(),
				v_request,
                m_uploadDevice
                );
	}
	else
	{
		QString v_content_type = "";
		QUrl v_unify_url = s_unify_url(v_request_url);
		if(s_url_info.contains(v_unify_url))
		{
			// HEAD op
			QNetworkAccessManager v_nam;
			v_nam.setCookieJar(0);
            QNetworkReply * v_headReply = v_nam.head(v_request);
            NetworkBatchRequest v_head_nbr(v_headReply);
			v_head_nbr.wait();
            v_content_type = v_headReply->header(QNetworkRequest::ContentTypeHeader).toString();
            qDebug() << "[T_MyHttpBackend::open()] v_content_type=" << v_content_type;
            QList<QByteArray> v_headerList = v_headReply->rawHeaderList();
            foreach(QByteArray v_first, v_headerList)
            {
                QByteArray v_second = v_headReply->rawHeader(v_first);
                qDebug() << v_first << v_second;
            }
            delete v_headReply;
		}
		if(v_content_type.startsWith("video/"))
		{
			m_httpReply = new T_HttpReply_3(
					QNetworkAccessBackend::operation(),
					v_request,
                    m_uploadDevice
                    );
		}
		else
		{
			m_httpReply = new T_HttpReply_1(
					QNetworkAccessBackend::operation(),
					v_request,
                    m_uploadDevice
                    );
		}
	}
	m_httpReply->setParent(this);
#ifndef QT_NO_OPENSSL
    if (m_pendingSslConfiguration)
        m_httpReply->setSslConfiguration(*m_pendingSslConfiguration);
    if (m_pendingIgnoreAllSslErrors)
        m_httpReply->ignoreSslErrors();
    m_httpReply->ignoreSslErrors(m_pendingIgnoreSslErrorsList);
#endif
    //authenticationRequired()
	connect(m_httpReply,
			SIGNAL(authenticationRequired(QAuthenticator*)),
			SLOT(replyAuthenticationRequired(QAuthenticator*)));
	//readyRead()
	connect(m_httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
	//metaDataChanged()
	connect(m_httpReply, SIGNAL(metaDataChanged()), SLOT(replyMetaDataChanged()));
	//finished()
	connect(m_httpReply, SIGNAL(finished()), SLOT(replyFinished()));
	//error()
	connect(m_httpReply, SIGNAL(error(QNetworkReply::NetworkError)),
			SLOT(replyError(QNetworkReply::NetworkError)));
	//uploadProgress()
	connect(m_httpReply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(replyUploadProgress(qint64,qint64)));
	//downloadProgress()
	connect(m_httpReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(replyDownloadProgress(qint64,qint64)));
#ifndef QT_NO_OPENSSL
    connect(m_httpReply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(sslErrors(QList<QSslError>)));
#endif
}

/* virtual */ void T_MyHttpBackend::downstreamReadyWrite()
{
	qDebug() << "[T_MyHttpBackend::downstreamReadyWrite()]";
	p_readFromHttp();
}

/* virtual */ void T_MyHttpBackend::copyFinished(QIODevice *dev)
{
	//TODO: delete this function if possible
	delete dev;
	T_MyHttpBackend::finished();
}
/* slot */ void T_MyHttpBackend::finished()
{
	QUrl v_request_url = QNetworkAccessBackend::request().url();
	qDebug() << "[T_MyHttpBackend::finished()]" << v_request_url;
	p_disconnectFromHttp();
	QNetworkAccessBackend::finished();
}

/* slot */ void T_MyHttpBackend::replyReadyRead()
{
	qDebug() << "T_MyHttpBackend::replyReadyRead()";
	p_readFromHttp();
}

/* slot */ void T_MyHttpBackend::replyFinished()
{
    //TODO: delete this line(?)
    // we haven't read everything yet. Wait some more.
    if (m_httpReply->bytesAvailable()) return;

	int statusCode = m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	if (statusCode >= 400) {
		// it's an error reply
		QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
													  "Error downloading %1 - server replied: %2"));
		msg = msg.arg(url().toString(), m_httpReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
		QNetworkAccessBackend::error(
				s_networkErrorCodeFromHttpStatus(statusCode, url()),
				msg);
	}
	T_MyHttpBackend::finished();
}

/* slot */ void T_MyHttpBackend::replyMetaDataChanged()
{
	QUrl v_request_url = QNetworkAccessBackend::request().url();
	qDebug() << "T_MyHttpBackend::replyMetaDataChanged():" << v_request_url;

	// HTTP Status
	QNetworkAccessBackend::setAttribute(
			QNetworkRequest::HttpStatusCodeAttribute,
			m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute));
	// HTTP Reason Phrase
	QNetworkAccessBackend::setAttribute(
			QNetworkRequest::HttpReasonPhraseAttribute,
			m_httpReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute));

	// HTTP Response Headers
	QList<QByteArray> v_headerList = m_httpReply->rawHeaderList();
	foreach(QByteArray v_first, v_headerList)
	{
		QByteArray v_second = m_httpReply->rawHeader(v_first);
		//qDebug() << "T_MyHttpBackend::replyHeaderChanged():" << v_first << v_second;
		qDebug() << v_first << v_second;
		QNetworkAccessBackend::setRawHeader(v_first, v_second);
	}
	// is it a redirection?
	const int statusCode = m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	p_checkForRedirect(statusCode);
	QNetworkAccessBackend::metaDataChanged();
}

/* slot */ void T_MyHttpBackend::namAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
{
	Q_UNUSED(reply);
	QNetworkAccessBackend::authenticationRequired(authenticator);
}

/* slot */ void T_MyHttpBackend::replyAuthenticationRequired(QAuthenticator *authenticator)
{
    qDebug() << "[T_MyHttpBackend::replyAuthenticationRequired()]" << authenticator->realm();
	QNetworkAccessBackend::authenticationRequired(authenticator);
}

/* slot */ void T_MyHttpBackend::replyError(QNetworkReply::NetworkError errorCode)
{
    T_HttpReply *v_reply = dynamic_cast<T_HttpReply *>(QObject::sender());
	QString errorString = v_reply ? v_reply->errorString() : QString::number(errorCode);
    qDebug() << "[T_MyHttpBackend::replyError()]" << errorCode << errorString;
	QNetworkAccessBackend::error(errorCode, errorString);
	T_MyHttpBackend::finished();
}

/* slot */ void T_MyHttpBackend::replyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
	qDebug() << "[T_MyHttpBackend::replyUploadProgress()]" << bytesSent << bytesTotal;
	QNetworkAccessBackend::emitReplyUploadProgress(bytesSent, bytesTotal);
}

/* slot */ void T_MyHttpBackend::replyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    //TODO: delete this
	qDebug() << "[T_MyHttpBackend::replyDownloadProgress()]" << bytesReceived << bytesTotal;
}

//TODO: delete m_pending...
void T_MyHttpBackend::ignoreSslErrors()
{
    if (m_httpReply)
        m_httpReply->ignoreSslErrors();
    else
        m_pendingIgnoreAllSslErrors = true;
}

void T_MyHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
{
    if (m_httpReply)
    {
        m_httpReply->ignoreSslErrors(errors);
    }
    else
    {
        // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
        // is called before QNetworkAccessManager::get() (or post(), etc.)
        m_pendingIgnoreSslErrorsList = errors;
    }
}

void T_MyHttpBackend::fetchSslConfiguration(QSslConfiguration & config) const
{
    if (m_httpReply)
        config = m_httpReply->sslConfiguration();
    else if (m_pendingSslConfiguration)
        config = *m_pendingSslConfiguration;
}

void T_MyHttpBackend::setSslConfiguration(const QSslConfiguration & config)
{
    if (m_httpReply)
        m_httpReply->setSslConfiguration(config);
    else if (m_pendingSslConfiguration)
        *m_pendingSslConfiguration = config;
    else
        m_pendingSslConfiguration = new QSslConfiguration(config);
}

void T_MyHttpBackend::p_disconnectFromHttp()
{
}

void T_MyHttpBackend::p_readFromHttp()
{
	QByteDataBuffer list;
	while (m_httpReply->bytesAvailable() != 0
		   && QNetworkAccessBackend::nextDownstreamBlockSize() != 0
		   && QNetworkAccessBackend::nextDownstreamBlockSize() > list.byteAmount()) {
		QByteArray v_bytes = m_httpReply->read(QNetworkAccessBackend::nextDownstreamBlockSize() - list.byteAmount());
		list.append(v_bytes);
	}
	if (!list.isEmpty()) QNetworkAccessBackend::writeDownstreamData(list);
}

void T_MyHttpBackend::p_checkForRedirect(const int statusCode)
{
	switch (statusCode) {
	case 301:				   // Moved Permanently
	case 302:				   // Found
	case 303:				   // See Other
	case 307:				   // Temporary Redirect
		// What do we do about the caching of the HTML note?
		// The response to a 303 MUST NOT be cached, while the response to
		// all of the others is cacheable if the headers indicate it to be
		QByteArray header = QNetworkAccessBackend::rawHeader("location");
		QUrl url = QUrl::fromEncoded(header);
		if (!url.isValid()) url = QUrl(QLatin1String(header));
        p_checkForRedirect_YouTube(url); //TODO: move to T_HttpReply
		QNetworkAccessBackend::redirectionRequested(url);
	}
}

//TODO: move to T_HttpReply
void T_MyHttpBackend::p_checkForRedirect_YouTube(QUrl url)
{
	QUrl v_request_url = QNetworkAccessBackend::request().url();
	qDebug() << "[p_checkForRedirect_YouTube()]" << v_request_url;
	qDebug() << "[p_checkForRedirect_YouTube()]" << url;
	QUrl v_request_url_unify = s_unify_url(v_request_url);
	if(s_url_info.contains(v_request_url_unify))
	{
		QUrl v_url_unify = s_unify_url(url);
		QString v_second = s_url_info.value(v_request_url_unify);
		s_url_info[v_url_unify] = v_second;
	}
}

static QNetworkReply::NetworkError s_networkErrorCodeFromHttpStatus(int httpStatusCode, const QUrl &url)
{
	QNetworkReply::NetworkError code;
	// we've got an error
	switch (httpStatusCode) {
	case 401:			   // Authorization required
		code = QNetworkReply::AuthenticationRequiredError;
		break;

	case 403:			   // Access denied
		code = QNetworkReply::ContentOperationNotPermittedError;
		break;

	case 404:			   // Not Found
		code = QNetworkReply::ContentNotFoundError;
		break;

	case 405:			   // Method Not Allowed
		code = QNetworkReply::ContentOperationNotPermittedError;
		break;

	case 407:
		code = QNetworkReply::ProxyAuthenticationRequiredError;
		break;

	default:
		if (httpStatusCode > 500) {
			// some kind of server error
			code = QNetworkReply::ProtocolUnknownError;
		} else if (httpStatusCode >= 400) {
			// content error we did not handle above
			code = QNetworkReply::UnknownContentError;
		} else {
			qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
					 httpStatusCode, qPrintable(url.toString()));
			code = QNetworkReply::ProtocolFailure;
		}
	}

	return code;
}
